﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Net;
using System.Xml;
using System.IO;
using CachingWebClient;
using NLog;
using Ionic.Zip;

namespace TheTVDB
{
   // 156D5D14C4A875F0

   // http://www.thetvdb.com/api/<apikey>/mirrors.xml

   public class TVDBApi
   {
      private static Logger Log = LogManager.GetCurrentClassLogger();

      private const string APIKey = "156D5D14C4A875F0";

      private const string mirrorPath = "http://www.thetvdb.com/api/" + APIKey + "/mirrors.xml";
      private const int xmlMirrorTypemask = 1;
      private const int bannerMirrorTypemask = 2;
      private const int zipMirrorTypemask = 4;

      private const string serverTimePath = "http://www.thetvdb.com/api/Updates.php?type=none";

      private const string SeriesPath = "<mirror>/api/" + APIKey + "/series/<seriesid>/all/en.zip";

      private const string SeriesLookupPath = "http://www.thetvdb.com/api/GetSeries.php?seriesname=<name>";

      private List<string> mXmlMirrors;
      private List<string> mBannerMirrors;
      private List<string> mZipMirrors;

      public bool SuppressOutput { get; set; }

      public class SeriesSearchResultItem
      {
         public int SeriesID { get; set; }
         public string Language { get; set; }
         public string Name { get; set; }
         public string Overview { get; set; }

         public SeriesSearchResultItem(int seriesID, string language, string name, string overview)
         {
            this.SeriesID = seriesID;
            this.Language = language;
            this.Name = name;
            this.Overview = overview;
         }
      }

      public TVDBApi()
      {
         SuppressOutput = false;
      }

      #region Mirrors
      private bool NeedsMirrors()
      {
         if ((mXmlMirrors == null) || (mBannerMirrors == null) || (mZipMirrors == null))
            return true;
         else
            return false;
      }

      public void GetMirrors()
      {
         Log.Debug("TVDB: Getting TVDB mirrors...");

         mXmlMirrors = new List<string>();
         mBannerMirrors = new List<string>();
         mZipMirrors = new List<string>();

         XmlDocument mirrors = GetWebXml(mirrorPath);

         foreach (XmlElement mirror in mirrors.SelectNodes("/Mirrors/Mirror"))
         {
            int typemask = int.Parse(mirror["typemask"].InnerText);

            if ((typemask & xmlMirrorTypemask) == xmlMirrorTypemask)
               mXmlMirrors.Add(mirror["mirrorpath"].InnerText);

            if ((typemask & bannerMirrorTypemask) == bannerMirrorTypemask)
               mBannerMirrors.Add(mirror["mirrorpath"].InnerText);

            if ((typemask & zipMirrorTypemask) == zipMirrorTypemask)
               mZipMirrors.Add(mirror["mirrorpath"].InnerText);
         }
      }

      private string GetXmlMirror()
      {
         Random r = new Random();

         return mXmlMirrors[r.Next(0, mXmlMirrors.Count - 1)];
      }
      private string GetBannerMirror()
      {
         Random r = new Random();
         return mBannerMirrors[r.Next(0, mBannerMirrors.Count - 1)];
      }

      private string GetZipMirror()
      {
         Random r = new Random();
         return mZipMirrors[r.Next(0, mZipMirrors.Count - 1)];
      }
      #endregion

      public void GetServerTime()
      {
      }

      public bool GetSeasonAndEpisode(XmlDocument seriesXml, string currentEpisodeName, ref int seasonNumber, ref int episodeNumber, ref string episodeName)
      {
         try
         {
            Log.Debug("TVDB: Searching for episode name: {0}", currentEpisodeName);

            // We remove spaces and other characters to get a better match rate
            string filteredEpisodeName = currentEpisodeName.Replace(" ", "").Replace(",", "").Replace("\"", "").ToUpper().Trim();

            int exactMatches = 0;

            foreach (XmlElement episode in seriesXml.SelectNodes("/Data/Episode"))
            {
               if ((episode["EpisodeName"] == null) || (string.IsNullOrEmpty(episode["EpisodeName"].InnerText)))
                  continue;

               string filteredResult = episode["EpisodeName"].InnerText.Replace(" ", "").Replace(",", "").Replace("\"", "").ToUpper().Trim();

               if (filteredResult == filteredEpisodeName)
               {
                  seasonNumber = int.Parse(episode["SeasonNumber"].InnerText);
                  episodeNumber = int.Parse(episode["EpisodeNumber"].InnerText);
                  episodeName = episode["EpisodeName"].InnerText;

                  Log.Debug("TVDB: Series Found:  Season: [{0}], Episode: [{1}], Name: [{2}]", seasonNumber, episodeNumber, episodeName);

                  exactMatches++;
               }
            }

            if (exactMatches == 1)
               return true;
            else if (exactMatches > 1)
               return false;
            else
               Log.Debug("TVDB: No episodes found.");

            return false;
         }
         catch (Exception ex)
         {
            Log.ErrorException("TVDB: Exception occured in GetSeasonAndEpisode.", ex);
            return false;
         }
      }

      public bool GetSeasonAndEpisode(XmlDocument seriesXml, int productionCode, ref int seasonNumber, ref int episodeNumber, ref string episodeName)
      {
         try
         {
            Log.Debug("TVDB: Searching for production code: {0}", productionCode);

            int exactMatches = 0;

            foreach (XmlElement episode in seriesXml.SelectNodes("/Data/Episode"))
            {
               int pCode = -1;

               if ((int.TryParse(episode["ProductionCode"].InnerText, out pCode)) && (pCode == productionCode))
               {
                  seasonNumber = int.Parse(episode["SeasonNumber"].InnerText);
                  episodeNumber = int.Parse(episode["EpisodeNumber"].InnerText);
                  episodeName = episode["EpisodeName"].InnerText;

                  Log.Debug("TVDB: Found:  Season: [{0}], Episode: [{1}], Name: [{2}]", seasonNumber, episodeNumber, episodeName);

                  exactMatches++;
               }
            }

            if (exactMatches == 1)
               return true;
            else if (exactMatches > 1)
               return false;
            else
               Log.Debug("TVDB: No episodes found.");

            return false;
         }
         catch (Exception ex)
         {
            Log.ErrorException("TVDB: Exception occured in GetSeasonAndEpisode.", ex);
            return false;
         }
      }

      public XmlDocument GetSeriesByID(int seriesId)
      {
         try
         {
            if (NeedsMirrors())
               GetMirrors();

            string zipPath = SeriesPath.Replace("<mirror>", GetZipMirror()).Replace("<seriesid>", seriesId.ToString());

            return GetXmlFromString(GetStringFromFileInsideZip(GetWebZip(zipPath), "en.xml"));
         }
         catch (Exception ex)
         {
            Log.ErrorException("TVDB: Exception occured in GetSeriesByID.", ex);
            return new XmlDocument();
         }
      }

      public List<SeriesSearchResultItem> FindSeries(string name)
      {
         if (NeedsMirrors())
            GetMirrors();

         Log.Debug("TVDB: Searching for series: [{0}]", name);

         List<SeriesSearchResultItem> items = new List<SeriesSearchResultItem>();

         string xmlPath = SeriesLookupPath.Replace("<name>", HttpUtility.UrlEncode(name));

         XmlDocument seriesItems = GetWebXml(xmlPath);

         foreach (XmlElement series in seriesItems.SelectNodes("/Data/Series"))
            items.Add(new SeriesSearchResultItem(int.Parse(series["seriesid"].InnerText), series["language"].InnerText, series["SeriesName"].InnerText, series["Overview"] == null ? string.Empty : series["Overview"].InnerText));

         return items;
      }

      #region Helper Methods
      private static XmlDocument GetXmlFromString(string xmlString)
      {
         XmlDocument doc = new XmlDocument();
         doc.LoadXml(xmlString);

         return doc;
      }

      private static string GetWebString(string url)
      {
         return CacheManager.GetWebString(url);
      }

      private static XmlDocument GetWebXml(string url)
      {
         return GetXmlFromString(GetWebString(url));
      }

      private static ZipFile GetWebZip(string url)
      {
         return CacheManager.GetWebZip(url);
      }

      private static string GetStringFromFileInsideZip(ZipFile zipFile, string filename)
      {
         // "en.xml", "banners.xml", "actors.xml"

         // Create memory stream
         MemoryStream ms = new MemoryStream();

         // Extract the file from the zipfile into our memory stream
         zipFile[filename].Extract(ms);

         // Seek back to the beginning of the memory stream
         ms.Seek(0, SeekOrigin.Begin);

         // Create a stream reader (from the memory stream) and read the results as a string
         return new StreamReader(ms).ReadToEnd();
      }

      #endregion

   }
}
